
package com.servequake.truemusic;

import de.jarnbjo.ogg.*;
import de.jarnbjo.flac.*;
import java.io.*;
import java.util.*;
import javax.sound.sampled.*;

/**
 * Audio for Ogg FLAC using de.jarnbjo.flac (J-Ogg)
  */
public class OggFlacAudio extends Audio implements Runnable
{
	String res = "";
	Playlist l = null;
	int frames = 0; //number of frames in the clip
	int pos = 0; //the next frame to play
	int current = 0; //the next frame in the decoder
	boolean pause = true;
	Thread runner;
	Tag t;
	
	PhysicalOggStream os = null;
	LogicalOggStream los = null;

	FlacStream vs;
	int channels;
	int sampleRate;
	VorbisComment comments;
	SourceDataLine line=null;
	FloatControl vol; 

	private static final int BUFFER_SIZE = 65536;
    byte[] buffer=new byte[BUFFER_SIZE];
  
  
  float seconds=0;
  float now=0;
  
	public String resource(){return res;}
	public void resource(String resource) throws Exception
	{
		this.res = resource; 
		t = new Tag(res);
		os=new FileStream(new RandomAccessFile(res, "r"));
		los=(LogicalOggStream)os.getLogicalStreams().iterator().next();
		if(los.getFormat()!=LogicalOggStream.FORMAT_FLAC) throw new RuntimeException("Not FLAC audio");
		vs=new FlacStream(los);
        comments=vs.getVorbisComment();
        t.title( comments.getTitle() );
        t.artist( comments.getArtist() );
		t.album( comments.getAlbum() );
		t.track( comments.getTrackNumber() );
		t.genre( comments.getGenre() );
		t.year( comments.getDate() );
		t.lyrics( comments.getComment("LYRICS") );	
		
		try{
			while(true){
				los.getNextOggPacket();
				frames++;
		}}catch(EndOfOggStreamException e){}
		
		seconds = los.getMaximumGranulePosition()/vs.getStreamInfo().getSampleRate();
		
		os=null;los=null;vs=null;
	}
	
	public void run()
	{
		try
		{
			while(runner==Thread.currentThread())
			{
				if(vs==null || pos<current)
				{
					os=new FileStream(new RandomAccessFile(res, "r"));
					los=(LogicalOggStream)os.getLogicalStreams().iterator().next();
					if(los.getFormat()!=LogicalOggStream.FORMAT_FLAC) throw new RuntimeException("Not FLAC audio");
					vs=new FlacStream(los);
					current=0;
				}
				
				if(line==null)
				{
					channels =vs.getStreamInfo().getChannels();
					sampleRate=vs.getStreamInfo().getSampleRate();
					line = getOutputLine(channels, sampleRate);
				}
				
				while(pos>current)
				{
					los.getNextOggPacket();
					current++;
				}
				
				int read=vs.readPcm(buffer, 0, buffer.length);
				current++;
				for(int i=0; i<read; i+=2)
				{
					// swap from big endian to little endian
					byte tB=buffer[i];
					buffer[i]=buffer[i+1];
					buffer[i+1]=tB;
				}
				
				line.write(buffer,0,read);
				pos++;
				now = (vs.getCurrentGranulePosition()/sampleRate);
				l.frameCompleted(pos,this);	

				while(pause && runner!=null) synchronized(this){wait();}
            }
        }
        catch(EndOfOggStreamException e)
		{
			l.playbackCompleted(this);
			runner=null;
			os=null;los=null;vs=null;
        }
		catch(Exception e)
		{
			e.printStackTrace();
			l.error(e,"Playback error");
		}
	}
	
	//TODO: inline into run
	SourceDataLine getOutputLine(int channels, int rate)
	{
		try
		{
			AudioFormat af = new AudioFormat( (float)rate, 16, channels, true, false );
			SourceDataLine outputLine = (SourceDataLine) AudioSystem.getLine(new DataLine.Info(SourceDataLine.class, af));
			outputLine.open(af);
			outputLine.start();
			
			vol = (FloatControl)outputLine.getControl(FloatControl.Type.MASTER_GAIN);
			if(vol!=null)vol.setValue(l.volume());

			return outputLine;
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		return null;
	}
	
	
	public synchronized void play()
	{
		if(runner==null) //stopped
		{
			pos = 0;
			pause = false;
			runner = new Thread(this);
			runner.start();
		}
		else if(pause) //paused
		{
			pause = false;
			notify();
		}
		else //playing
		{
			pos = 0;
		}
	}

	public synchronized void pause()
	{
		if(runner==null) //stopped
		{
		}
		else if(pause) //paused
		{
			pause = false;
			notify();
		}
		else //playing
		{
			pause = true;
		}
	}
	
	public synchronized void stop()
	{
		if(runner==null) //stopped
		{
		}
		else if(pause) //paused
		{
			runner = null;
			notify();
		}
		else //playing
		{
			runner = null;
		}
		
		pos = 0;
		pause = true;
		runner = null;
		line = null;
		os=null;los=null;vs=null;
		l.frameCompleted(0,this);
	}
	
	public synchronized void seek(int frame)
	{
		pos = frame;
	}
	
	public void volume(int level)
	{
		if(vol!=null) vol.setValue(level);
	}
	
	public int framePosition() {return pos;}
	public int frameLength() {return frames;}
	
	public int timePosition() {return (int)now;}
	public int timeLength() {return (int)seconds;}
	
	boolean isPlaying(){return !pause;}
	boolean isSeekable() {return true;}
	
	public Playlist listener() { return l; }
	public void listener(Playlist l) { this.l=l; }
	
	public Tag tag() {return t;}

	public String[] formatExtensions(){return new String[]{".ogg"};}
	public String formatName(){return "Ogg FLAC";}	
}
